// Copyright 2023 Google LLC
// Copyright lowRISC contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module clkgen_xilultrascaleplus
    #(parameter int ClockFrequencyMhz = 80,
      // Add BUFG if not done by downstream logic
      parameter bit AddClkBuf = 1)
    (input clk_i,
     input clk_n_i,
     input rst_ni,
     input srst_ni,
     output clk_main_o,
     output clk_48MHz_o,
     output clk_aon_o,
     output rst_no,
     output locked_o);
  logic locked_pll;
  logic io_clk_buf;
  logic io_rst_buf_n;
  logic clk_10_buf;
  logic clk_10_unbuf;
  logic clk_fb_buf;
  logic clk_fb_unbuf;
  logic clk_48_buf;
  logic clk_48_unbuf;
  logic clk_aon_buf;
  logic clk_aon_unbuf;
  logic clk_ibufds_o;

  // Input IBUFDS conver diff-pair to single-end
  IBUFDS clk_ibufds(.I(clk_i),
                    .IB(clk_n_i),
                    .O(clk_ibufds_o));

  localparam real CLKOUT0_DIVIDE_F_CALC = 1200.0 / ClockFrequencyMhz;

  MMCME2_ADV #(
          .BANDWIDTH("OPTIMIZED"),
          .COMPENSATION("ZHOLD"),
          .STARTUP_WAIT("FALSE"),
          .DIVCLK_DIVIDE(1),
          .CLKFBOUT_MULT_F(12.000),
          .CLKFBOUT_PHASE(0.000),
          .CLKOUT0_DIVIDE_F(CLKOUT0_DIVIDE_F_CALC),
          .CLKOUT0_PHASE(0.000),
          .CLKOUT0_DUTY_CYCLE(0.500),
          .CLKOUT1_DIVIDE(25),
          .CLKOUT1_PHASE(0.000),
          .CLKOUT1_DUTY_CYCLE(0.500),
          // With CLKOUT4_CASCADE, CLKOUT6's divider is an input to CLKOUT4's
          // divider. The effective ratio is a multiplication of the two.
          .CLKOUT4_DIVIDE(40),
          .CLKOUT4_PHASE(0.000),
          .CLKOUT4_DUTY_CYCLE(0.500),
          .CLKOUT4_CASCADE("TRUE"),
          .CLKOUT6_DIVIDE(120),
          .CLKIN1_PERIOD(10.000))
      pll(.CLKFBOUT(clk_fb_unbuf),
          .CLKFBOUTB(),
          .CLKOUT0(clk_10_unbuf),
          .CLKOUT0B(),
          .CLKOUT1(clk_48_unbuf),
          .CLKOUT1B(),
          .CLKOUT2(),
          .CLKOUT2B(),
          .CLKOUT3(),
          .CLKOUT3B(),
          .CLKOUT4(clk_aon_unbuf),
          .CLKOUT5(),
          .CLKOUT6(),
          // Input clock control
          .CLKFBIN(clk_fb_buf),
          .CLKIN1(clk_ibufds_o),
          .CLKIN2(1'b0),
          // Tied to always select the primary input clock
          .CLKINSEL(1'b1),
          // Ports for dynamic reconfiguration
          .DADDR(7'h0),
          .DCLK(1'b0),
          .DEN(1'b0),
          .DI(16'h0),
          .DO(),
          .DRDY(),
          .DWE(1'b0),
          // Phase shift signals
          .PSCLK(1'b0),
          .PSEN(1'b0),
          .PSINCDEC(1'b0),
          .PSDONE(),
          // Other control and status signals
          .CLKFBSTOPPED(),
          .CLKINSTOPPED(),
          .LOCKED(locked_pll),
          .PWRDWN(1'b0),
          // Do not reset MMCM on external reset, otherwise ILA disconnects at a
          // reset
          .RST(1'b0));

  // output buffering
  BUFGCE clk_fb_bufgce(.I(clk_fb_unbuf),
                       .O(clk_fb_buf));

  BUFGCE clk_aon_bufgce(.I(clk_aon_unbuf),
                        .O(clk_aon_buf));

  if (AddClkBuf == 1) begin : gen_clk_bufs
    BUFGCE clk_10_bufgce(.I(clk_10_unbuf),
                         .O(clk_10_buf));

    BUFGCE clk_48_bufgce(.I(clk_48_unbuf),
                         .O(clk_48_buf));

  end else begin : gen_no_clk_bufs
    // BUFGs added by downstream modules, no need to add here
    assign clk_10_buf = clk_10_unbuf;
    assign clk_48_buf = clk_48_unbuf;
  end

  // outputs
  // clock
  assign clk_main_o = clk_10_buf;
  assign clk_48MHz_o = clk_48_buf;
  assign clk_aon_o = clk_aon_buf;

  // reset
  assign rst_no = locked_pll & rst_ni & srst_ni;
  assign locked_o = locked_pll;
endmodule
